package com.mazaiting.redeye.interceptors;

import com.mazaiting.redeye.component.RedisTokenManager;
import com.mazaiting.redeye.config.GlobalConfig;
import com.mazaiting.redeye.config.TipConfig;
import com.mazaiting.redeye.domain.Request;
import com.mazaiting.redeye.domain.Response;
import com.mazaiting.redeye.domain.ResponseCode;
import com.mazaiting.redeye.domain.UserBean;
import com.mazaiting.redeye.wrapper.RequestWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.json.JacksonJsonParser;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.Map;

/***
 *
 *
 *                                                    __----~~~~~~~~~~~------___
 *                                   .  .   ~~//====......          __--~ ~~
 *                   -.            \_|//     |||\\  ~~~~~~::::... /~
 *                ___-==_       _-~o~  \/    |||  \\            _/~~-
 *        __---~~~.==~||\=_    -_--~/_-~|-   |\\   \\        _/~
 *    _-~~     .=~    |  \\-_    '-~7  /-   /  ||    \      /
 *  .~       .~       |   \\ -_    /  /-   /   ||      \   /
 * /  ____  /         |     \\ ~-_/  /|- _/   .||       \ /
 * |~~    ~~|--~~~~--_ \     ~==-/   | \~--===~~        .\
 *          '         ~-|      /|    |-~\~~       __--~~
 *                      |-~~-_/ |    |   ~\_   _-~            /\
 *                           /  \     \__   \/~                \__
 *                       _--~ _/ | .-~~____--~-/                  ~~==.
 *                      ((->/~   '.|||' -_|    ~~-/ ,              . _||
 *                                 -_     ~\      ~~---l__i__i__i--~~_/
 *                                 _-~-__   ~)  \--______________--~~
 *                               //.-~~~-~_--~- |-------~~~~~~~~
 *                                      //.-~~~--\
 *                               神兽保佑
 *                              代码无BUG!
 * @author mazaiting
 * @date 2019-06-26
 * @decription Session 拦截器
 */
@Component
public class SessionInterceptor implements HandlerInterceptor {
    /**
     * 带空格的 JSON 类型
     */
    private static final String APPLICATION_JSON_UTF8_VALUE = "application/json; charset=UTF-8";
    /**
     * 日志
     */
    private static final Logger mLogger = LoggerFactory.getLogger(SessionInterceptor.class);
    /**
     * 不需要 Token 的地址
     */
    private static final String[] URLS = {"/user/login", "/user/register"};

    /**
     * Redis Token 服务
     */
    private final RedisTokenManager mRedisTokenManager;

    @Autowired
    public SessionInterceptor(RedisTokenManager mRedisTokenManager) {
        this.mRedisTokenManager = mRedisTokenManager;
    }


    /**
     * 拦截逻辑
     * 1. 判断是否为 POST 请求
     * 2. 判断是否登录接口/注册
     * 3. 判断是否为 JSON
     * 4. 获取 Token, 并检测其是否有效
     *
     * @param request  请求
     * @param response 响应
     * @param handler  对象
     * @return 是否继续执行
     * @throws Exception 异常
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        mLogger.debug("SessionInterceptor");
        // 获取地址
        String requestUrl = request.getRequestURL().toString();
        mLogger.debug("url: " + requestUrl);
        // 获取 session
        HttpSession session = request.getSession();
        // 获取 Session ID
        String sessionId = session.getId();
        mLogger.debug("sessionId: " + sessionId);
        // 获取请求方法
        String requestMethod = request.getMethod();
        mLogger.debug("requestMethod: " + requestMethod);
        // 获取请求路径
        String servletPath = request.getServletPath();
        // 检测路径是否为空
        if (StringUtils.isEmpty(servletPath)) {
            // 从请求地址获取路径
            servletPath = requestUrl.substring(GlobalConfig.URL.length() - 1);
        }
        mLogger.debug("servletPath: " + servletPath);
        String contentType = request.getContentType();
        mLogger.debug("contentType: " + contentType);
        // 1. 判断是否为 POST 请求
        if (HttpMethod.POST.matches(requestMethod)) {
            // 2. 判断是否需要 Token 验证
//            if (hasToken(servletPath)) {
            mLogger.debug("isJson: " + isJson(request));
            // 3. 判断是否为 JSON 数据
            if (isJson(request)) {
                // 获取用户信息
                Request<UserBean> userBeanRequest = mRedisTokenManager.getUserBySessionId(sessionId);
                // 如果为空, 说明为第一次请求
                if (null == userBeanRequest) {
                    mLogger.debug("hasToken: " + hasToken(servletPath));
                    // 判断是否为注册/登录
                    // 如果是注册/登录接口, 不做处理
                    if (hasToken(servletPath)) {
                        // 检测是否携带 Token, 若携带则提示 Token 失效, 反之提示请先去登录
                        // 创建包装类, 获取请求体
//                        String body = new RequestWrapper(request).getBody();
//                        mLogger.debug("body: " + body);
//                        Map<String, Object> map = new JacksonJsonParser().parseMap(body);
//                        // 检测是否包含 Token
//                        if (map.containsKey("token")) {
//                            // 获取 Token
//                            String token = (String) map.get("token");
//                            mLogger.debug("token: " + token);
//                            // 检测 Token 是否为空
//                            if (StringUtils.isEmpty(token)) {
//                                setResponse(response, TipConfig.TOKEN_NOT_VALID);
//                                return false;
//                            } else {
//
//                            }
//                        } else {
//                            setResponse(response, TipConfig.TOKEN_NOT_VALID);
//                            return false;
//                        }
                        setResponse(response, TipConfig.TOKEN_NOT_VALID);
                        return false;
                    }
                } else {
                    // 非第一次请求
                    // 检测是否为登录/注册接口
                    if (isDifferent(servletPath, URLS[0])) {
                        setResponse(response, TipConfig.TOKEN_EXIST);
                        return false;
                    } else {
                        // 检测是否携带 Token, 若携带则提示 Token 失效, 反之提示请先去登录
                        // 创建包装类, 获取请求体
                        String body = new RequestWrapper(request).getBody();
                        mLogger.debug("body: " + body);
                        Map<String, Object> map = new JacksonJsonParser().parseMap(body);
                        // 检测是否包含 Token
                        if (map.containsKey("token")) {
                            // 获取 Token
                            String token = (String) map.get("token");
                            mLogger.debug("token: " + token);
                            // 检测 Token 是否为空, 并检测 Token 是否相同
                            if (StringUtils.isEmpty(token) || !token.equals(userBeanRequest.getToken())) {
                                setResponse(response, TipConfig.TOKEN_NOT_EXIST);
                                return false;
                            } else {
                                mLogger.debug("刷新 Token");
                                // 刷新 Token
                                mRedisTokenManager.refreshUserToken(sessionId);
                            }
                        } else {
                            setResponse(response, TipConfig.TOKEN_NOT_EXIST);
                            return false;
                        }
                    }
                }
            } else if (isFormData(request)) {
                // 检测是否为文件上传
                mLogger.debug("表单数据");
            } else {
                // 请求体为空
                setResponse(response, TipConfig.TOKEN_NOT_EXIST);
                return false;
            }
//            }
        }

        return true;
    }

    /**
     * 设置响应内容
     *
     * @param response 响应内容
     * @param msg      消息内容
     * @throws IOException IO 异常
     */
    private void setResponse(HttpServletResponse response, String msg) throws IOException {
        // 设置响应类型
        response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
        // 创建响应对象
        Response result = new Response();
        // 设置响应内容
        result.setResponse(ResponseCode.ERROR, msg);
        // 返回数据
        response.getWriter().println(result.toJson());
    }

    /**
     * 判断 URL 是否相同
     *
     * @param requestUrl 请求地址
     * @param url        固定地址
     * @return true: 相同; false: 不相同
     */
    private boolean isDifferent(String requestUrl, String url) {
        return url.equals(requestUrl);
    }

    /**
     * 判断是否需要 Token
     *
     * @param requestUrl 地址
     * @return true: 需要 Token; false: 不需要
     */
    private boolean hasToken(String requestUrl) {
        boolean isNeed = true;
        // 遍历检测是否包含
        for (String url : URLS) {
            // 匹配成功
            if (url.equals(requestUrl)) {
                // 标记为不需要
                isNeed = false;
            }
        }
        return isNeed;
    }

    /**
     * 判断本次请求的数据类型是否为json
     *
     * @param request request
     * @return true: 是 JSON 数据; false: 非 json 数据
     */
    private boolean isJson(HttpServletRequest request) {
        if (!StringUtils.isEmpty(request.getContentType())) {
            return MediaType.APPLICATION_JSON_VALUE.equals(request.getContentType())
                    || MediaType.APPLICATION_JSON_UTF8_VALUE.equals(request.getContentType())
                    || APPLICATION_JSON_UTF8_VALUE.equals(request.getContentType());
        }

        return false;
    }

    /**
     * 判断本次请求的数据类型是否为表单上传 multipart/form-data
     *
     * @param request request
     * @return true: 是 表单 数据; false: 非 表单 数据
     */
    private boolean isFormData(HttpServletRequest request) {
        if (!StringUtils.isEmpty(request.getContentType())) {
            return request.getContentType().startsWith(MediaType.MULTIPART_FORM_DATA_VALUE);
        }

        return false;
    }
}
